"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
// 解析vue
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
const chalk_1 = __importDefault(require("chalk"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const crypto_1 = __importDefault(require("crypto"));
const component_compiler_utils_1 = require("@vue/component-compiler-utils");
const utils_1 = require("../../utils");
const fsPromises = fs_1.default.promises;
const cachedMap = new Map();
exports.default = fastify_plugin_1.default((instance, options) => __awaiter(void 0, void 0, void 0, function* () {
    const compiler = utils_1.resolveCompiler(options.root);
    instance.addHook('preHandler', (request, reply) => __awaiter(void 0, void 0, void 0, function* () {
        const url = request.url.split('?')[0];
        if (url.split('?')[0].endsWith('.vue')) {
            let descriptor = yield genDescriptor(url);
            // if genDescriptor cannot handle all thre error
            if (!descriptor) {
                throw new Error(`[tserve error]:
                        generate descriptor error on resolve vue file.file path:${url}.`);
            }
            let scopeId;
            if (descriptor.styles.some(s => s.scoped)) {
                // generate hash
                scopeId = 'data-v-' + crypto_1.default.createHash('sha1').update(url).digest('hex').substring(0, 6);
            }
            if (request.query.type) {
                // if request is type of template
                if (request.query.type === 'template' && descriptor.template) {
                    try {
                        const compileTemplateResult = component_compiler_utils_1.compileTemplate({
                            source: descriptor.template.content,
                            filename: url,
                            compiler,
                        });
                        reply.header('Content-Type', 'application/javascript');
                        return reply.send(`${compileTemplateResult.code}\n export { render, staticRenderFns }`);
                    }
                    catch (e) {
                        /* handle error */
                        throw new Error(e);
                    }
                }
                if (request.query.type === 'style' && descriptor.styles) {
                    try {
                        // TODO: fix compile style
                        const compileStyleResult = yield component_compiler_utils_1.compileStyleAsync({
                            source: descriptor.styles[request.query.index].content,
                            filename: url,
                            id: scopeId ? scopeId : url,
                            scoped: scopeId !== undefined,
                            // for scss sass
                            preprocessLang: descriptor.styles[request.query.index].lang
                        });
                        reply.type('text/css');
                        return reply.send(compileStyleResult.code);
                    }
                    catch (e) {
                        /* handle error */
                        throw new Error(e);
                    }
                }
            }
            let code = `import { vueHotLoad } from '/tserve/vue.js'\n`;
            if (descriptor.template) {
                code += `import { render as _render, staticRenderFns as _staticRenderFns } from '${url}?type=template&t=${Date.now()}'\n`;
            }
            if (descriptor.script) {
                code += descriptor.script.content.replace(/export\s+default/, 'const __script = ');
                if (descriptor.template) {
                    code += `\n__script.render = _render
                   \n__script.staticRenderFns = _staticRenderFns`;
                }
                if (scopeId) {
                    code += `\n__script._scopeId='${scopeId}'`;
                }
                // for hot module reload
                code += `\nvueHotLoad('${scopeId === null || scopeId === void 0 ? void 0 : scopeId.split('-')[2]}', __script) `;
                code += `\n export default __script`;
            }
            // if has style
            if (descriptor.styles) {
                for (const coon of instance.esConnections) {
                    coon.write(`event:css-update\ndata:{"id":"${url}","length":${descriptor.styles.length}}\n\n`);
                }
            }
            reply.header('Content-Type', 'application/javascript');
            return reply.send(code);
        }
    }));
    // fot hot reload
    instance.get('/tserve/vue.js', (request, reply) => __awaiter(void 0, void 0, void 0, function* () {
        // console.debug(__dirname, process.cwd(), path.dirname('node_modules/tserve'))
        // FIXME:because in development evvironment thie tserve module is symbol link for 
        // test package, this __dirname is not the node_modules's relative path of tserve,
        // so we need to rebuild it.
        return reply.sendFile(path_1.default.join('node_modules', __dirname.substring(__dirname.indexOf('/tserve') + 1), 'client.js'));
    }));
    // TODO: handle file change
    // watch file
    instance.watcher.on('change', (path) => __awaiter(void 0, void 0, void 0, function* () {
        if (path.endsWith('vue')) {
            path = `/${path}`;
            cachedMap.delete(path);
            console.log(`File changed on path: ${path}`);
            const descriptor = yield genDescriptor(path);
            if (!descriptor) {
                console.error(`[tserve error]:
                      generate descriptor error on resolve vue file.path: ${path}`);
                return;
            }
            const data = {
                id: path,
                _scopeId: crypto_1.default.createHash('sha1').update(path).digest('hex').substring(0, 6)
            };
            instance.esConnections.forEach(coon => {
                const sendmsg = `event:vue-update\ndata:${JSON.stringify(data)}\n\n`;
                console.debug(`sendmsg:${sendmsg}`);
                coon.write(sendmsg);
            });
        }
    }));
    // generate sfc descriptor
    function genDescriptor(url) {
        return __awaiter(this, void 0, void 0, function* () {
            // if cached
            if (cachedMap.has(url)) {
                return cachedMap.get(url);
            }
            console.debug(`loading vue file:${url}`);
            // TODO: parse vue
            const root = options.root;
            const filePath = path_1.default.resolve(root, url.substring(1));
            let fileContent = '';
            try {
                fileContent = yield fsPromises.readFile(filePath, { encoding: 'utf-8', flag: 'r' });
            }
            catch (e) {
                /* handle error */
                console.error(e, chalk_1.default.red(`file path ${filePath}`));
            }
            // compile
            try {
                const descriptor = component_compiler_utils_1.parse({
                    source: fileContent,
                    filename: filePath,
                    compiler,
                    needMap: true,
                    sourceRoot: options.root
                });
                cachedMap.set(url, descriptor);
                return descriptor;
            }
            catch (e) {
                /* handle error */
                console.error(chalk_1.default.red(e), `fileContent:${fileContent}`);
                throw new Error(`cannot parse file: ${fileContent}`);
            }
        });
    }
}));
